Skip to content

feat: four-point hook SPI (incoming, pre-storage, post-commit, outgoing) - #52

Merged
degoke merged 6 commits into
mainfrom
degoke/hook-spi-86c6
Sep 21, 2026
Merged

degoke merged 6 commits into
mainfrom
degoke/hook-spi-86c6

Conversation

@degoke

@degoke degoke commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Item 3 of the HAPI-inspired build list: a small hook SPI so people can extend HAIStack without forking. Four pointcuts only — not HAPI’s interceptor bus.

Merged main after #50 (REST table stakes). Conflicts were all overlapping edits, not competing intent: HTTP keeps both pkg/core and pkg/hooks imports; Patch/If-Match still go through patchAndCommit so ActionPatch stays, and the apply step uses applyPatchDocument for FHIR Patch.

Points

Point Where Can abort? Can mutate resource?
incoming HTTP, after routing, before the handler yes no (body may not be parsed yet; bundle type is parsed first for POST /fhir)
pre-storage core write path, after validation, before version stamp/persist yes yes (body only; identity is rejected)
post-commit core, after the write session commits no (errors are ignored so a successful write is not reported as failure) observe only
outgoing HTTP, before a resource envelope or search/history/$everything bundle is serialized yes yes

Register with hooks.NewRegistry().On(point, fn) and pass the same registry to core and HTTP via runtime.Builder.WithHooks.

Unknown points are rejected at registration. Hooks run in order; the first error stops the chain.

Review follow-ups

  • PATCH pre-storage and post-commit use ActionPatch (persist is still an update)
  • Outgoing hooks run for search and history bundles
  • Patient $everything uses writeBundleJSON so outgoing hooks see the compartment searchset (ActionOperation, query type Patient, envelope Bundle)
  • Transaction post-commit carries the real response Bundle
  • Update/patch pre-storage receive Previous
  • Pre-storage cannot change id / resourceType
  • Outgoing ResourceType is the queried type (envelope remains Bundle)
  • HTTP PATCH is authorized as "patch"
  • /metadata runs outgoing
  • Batch POST /fhir uses ActionBatch for incoming, outgoing, and post-commit (not ActionTransaction)

Tests

  • Registry order/stop/mutate in pkg/hooks
  • Pre-storage reject + mutate, post-commit observe-without-fail in pkg/core
  • Incoming reject and outgoing mutation in pkg/http
  • PATCH ActionPatch, outgoing search, Previous on update, identity mutation rejected
  • Batch ActionBatch on HTTP incoming/outgoing and core post-commit
  • Outgoing hook on Patient $everything bundle
Open in Web Open in Cursor 

cursoragent and others added 4 commits September 19, 2026 11:16
Add pkg/hooks with a small Registry so callers can extend the server
without forking. HTTP runs incoming after routing and outgoing before a
resource envelope is written. Core runs pre-storage before persist and
post-commit after commit (post-commit errors do not fail the write).

Wire the same registry through runtime.Builder.WithHooks.

Co-authored-by: Adegoke Adewoye <[email protected]>
Patch no longer delegates to Update, so pre-storage and post-commit
see ActionPatch while persist stays VersionActionUpdate. Search and
history bundles run outgoing hooks, transaction post-commit gets the
real response bundle, update/patch pass Previous, and identity
mutations after pre-storage are rejected.

Co-authored-by: Adegoke Adewoye <[email protected]>
Keep the incoming Event.ResourceType on outgoing search/history so PHI
hooks still see the queried type while Resource is the Bundle. Authorize
HTTP PATCH as "patch" (SMART still maps to OpUpdate). Route /metadata
through writeEnvelope so outgoing hooks can redact CapabilityStatement.

Co-authored-by: Adegoke Adewoye <[email protected]>
POST /fhir incoming and outgoing hooks used ActionTransaction for batch
bundles because routing cannot see Bundle.type. Parse the body first,
then fire incoming/outgoing as batch. Core ProcessBatchBundle now runs
post-commit with the response Bundle.

Co-authored-by: Adegoke Adewoye <[email protected]>
@degoke
degoke marked this pull request as ready for review September 21, 2026 11:37
cursoragent and others added 2 commits September 21, 2026 11:39
Keep hook SPI write/batch paths and take FHIR Patch plus HTTP core
imports from #50. PatchIfMatch still goes through patchAndCommit so
ActionPatch and pre-storage stay on the If-Match path.

Co-authored-by: Adegoke Adewoye <[email protected]>
Route $everything through writeBundleJSON like search and history so an
outgoing hook can inspect or redact the compartment searchset.

Co-authored-by: Adegoke Adewoye <[email protected]>
@degoke
degoke merged commit 8d2e704 into main Sep 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants